Preserve $OUT_DIR across rebuilds
authorAlex Crichton <alex@alexcrichton.com>
Sat, 16 Aug 2014 01:15:13 +0000 (18:15 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 19 Aug 2014 06:43:27 +0000 (23:43 -0700)
The original choice for completely destroying $OUT_DIR was to focus on
repeatable builds to ensure that stale items in $OUT_DIR don't affect later
compilations. This commit moves this responsibility from cargo to the build
command itself.

Build commands are now responsible for cleaning out old artifacts and ensuring
that when invoked the state of $OUT_DIR is as it would be if it started with an
empty $OUT_DIR. This will allow for very long build commands based on `make` to
have a much faster incremental build time as they won't have to rebuild
everything when updated.

This can cause non-repeatable builds with build commands if stale artifacts are
never removed (but still used in the rust compilation step), but this will now
be considered a bug in the build command rather than for cargo itself.

Closes #382

src/cargo/ops/cargo_rustc/mod.rs
tests/test_cargo_compile.rs

index 1bf38eac42d29aa715503b582906bc651e312c06..0bb383d6b53fd176e7c992380f744c36fd9037f0 100644 (file)
@@ -164,6 +164,7 @@ fn compile_custom(pkg: &Package, cmd: &str,
     //       may be building a C lib for a plugin
     let layout = cx.layout(KindTarget);
     let output = layout.native(pkg);
+    let old_output = layout.proxy().old_native(pkg);
     let mut p = process(cmd.next().unwrap(), pkg, cx)
                      .env("OUT_DIR", Some(&output))
                      .env("DEPS_DIR", Some(&output))
@@ -173,7 +174,11 @@ fn compile_custom(pkg: &Package, cmd: &str,
     }
     Ok(proc() {
         if first {
-            try!(fs::mkdir(&output, UserRWX).chain_error(|| {
+            try!(if old_output.exists() {
+                fs::rename(&old_output, &output)
+            } else {
+                fs::mkdir(&output, UserRWX)
+            }.chain_error(|| {
                 internal("failed to create output directory for build command")
             }));
         }
index c5834741f158c30e5b8dcf614fcf4b02b86c9e58..9f916ea4194df624a7e3708ce5783f0d49ede8a9 100644 (file)
@@ -1525,3 +1525,54 @@ test!(freshness_ignores_excluded {
 {fresh} foo v0.0.0 ({url})
 ", fresh = FRESH, url = foo.url())));
 })
+
+test!(rebuild_preserves_out_dir {
+    let mut build = project("builder");
+    build = build
+        .file("Cargo.toml", r#"
+            [package]
+            name = "build"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+        "#)
+        .file("src/main.rs", r#"
+            use std::os;
+            use std::io::File;
+
+            fn main() {{
+                let path = Path::new(os::getenv("OUT_DIR").unwrap()).join("foo");
+                if os::getenv("FIRST").is_some() {
+                    File::create(&path).unwrap();
+                } else {
+                    File::create(&path).unwrap();
+                }
+            }}
+        "#);
+    assert_that(build.cargo_process("cargo-build"), execs().with_status(0));
+
+    let foo = project("foo")
+        .file("Cargo.toml", format!(r#"
+            [package]
+            name = "foo"
+            version = "0.0.0"
+            authors = []
+            build = '{}'
+        "#, build.bin("build").display()).as_slice())
+        .file("src/lib.rs", "pub fn bar() -> int { 1 }");
+    foo.build();
+    foo.root().move_into_the_past().assert();
+
+    assert_that(foo.process(cargo_dir().join("cargo-build"))
+                   .env("FIRST", Some("1")),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} foo v0.0.0 ({url})
+", compiling = COMPILING, url = foo.url())));
+
+    File::create(&foo.root().join("src/bar.rs")).assert();
+    assert_that(foo.process(cargo_dir().join("cargo-build")),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} foo v0.0.0 ({url})
+", compiling = COMPILING, url = foo.url())));
+})